﻿using System;
using System.Collections.Generic;
using System.Drawing;
using System.Linq;
using RRRSRoguelike.Entities;
using RRRSRoguelike.Entities.Creatures;
using RRRSRoguelike.Entities.Props;
using RRRSRoguelike.Enums;
using RRRSRoguelike.Managers;
using RRRSRoguelikeData;

namespace RRRSRoguelike.Entities
{
	/// <summary>
	/// Describes the dungeon level.
	/// </summary>
	public class DungeonLevel
	{

		public LevelData Data;
		public IList<Tile> walls;

		public List<Tile[,]> layers = new List<Tile[,]>();

		public Tile OpenTile;
		public int level;

		public DungeonLevel(int level, int xMax, int yMax)
		{
			this.level = level;
			walls = new List<Tile>();

			for (var i = 0; i < Enum.GetNames(typeof(Layers)).Count(); i++)
				layers.Add(new Tile[xMax, yMax]);

			OpenTile = new Tile();

			BuildRandomDungeon();
		}

		public override string ToString()
		{
			return "Dungeon Level: " + level;
		}

		private void InitializeTiles()
		{
			for (var i = 0; i < Constants.DungeonHeight; i++)
			{
				for (var j = 0; j < Constants.DungeonWidth; j++)
				{
					layers[(int)Layers.Base][j, i] =
						new Tile(new Point(i, j), GameManager.Instance.Dungeon.Data.Tiles["Floor"]);
				}
			}
		}

		public static Point GetNewPosition(Point position, Direction direction, int distance)
		{
			var newPoint = position;
			switch (direction)
			{
				case Direction.N:
					newPoint.Y -= distance;
					break;
				case Direction.S:
					newPoint.Y += distance;
					break;
				case Direction.E:
					newPoint.X += distance;
					break;
				case Direction.W:
					newPoint.X -= distance;
					break;
			}
			return newPoint;
		}

		private void BuildRandomDungeon()
		{
			InitializeTiles();
			for (var i = 0; i < Constants.DungeonWidth; i++)
			{
				var topTile = new Tile(new Point(i, 0),
									GameManager.Instance.Dungeon.Data.Tiles["Wall"]);
				walls.Add(topTile);
				var bottomTile = new Tile(new Point(i, Constants.DungeonHeight - 1),
									   GameManager.Instance.Dungeon.Data.Tiles["Wall"]);
				walls.Add(bottomTile);
			}

			for (var i = 0; i < Constants.DungeonHeight; i++)
			{
				var leftTile = new Tile(new Point(0, i),
									 GameManager.Instance.Dungeon.Data.Tiles["Wall"]);
				walls.Add(leftTile);
				var rightTile = new Tile(new Point(Constants.DungeonWidth - 1, i),
									  GameManager.Instance.Dungeon.Data.Tiles["Wall"]);
				walls.Add(rightTile);
			}
		}

		public bool IsOKToMove(Point point)
		{
			//not ok
			var isOk = false;
			if (IsInBounds(point))
			{
				//ok if in bounds
				isOk = true;
				// not ok if any layers tile is unwalkable. (allows for unwalkable effects. illusion spells anyone?)
				var tiles = GetTiles(point);
				foreach (var objectTile in tiles)
				{
					var tile = (Tile)objectTile;
					if (!tile.Walkable) isOk = false;
				}
			}
			return isOk;
		}
		/// <summary>
		/// Checks for tiles of a certain type at a certain point.  Replace Contains Trap.
		/// </summary>
		/// <param name="p"></param>
		/// <param name="tileType"></param>
		/// <returns></returns>
		public bool SquareContainsTile(Point p,Type tileType)
		{
			//not ok
			var containsTile = false;
			if (IsInBounds(p))
			{
				//ok if in bounds
				// not ok if any layers tile is unwalkable. (allows for unwalkable effects. illusion spells anyone?)
				List<Tile> tileObjects = GetTiles(p);
				if (tileObjects.Where(t => t.GetType() == tileType).Any())
							containsTile = true;
	
				
			}
			return containsTile;
		}

		public bool IsInBounds(Point p)
		{
			return
				p.X >= 0 && p.X < Constants.DungeonWidth &&
				p.Y >= 0 && p.Y < Constants.DungeonHeight;
		}

		public static Point ClampToDungeon(Point p, bool insideWalls = false)
		{
			if (insideWalls)
			{
				p.X = Math.Max(Math.Min(p.X, Constants.DungeonWidth - 2), 1);
				p.Y = Math.Max(Math.Min(p.Y, Constants.DungeonHeight - 2), 1);
			}
			else
			{
				//includes walls
				p.X = Math.Max(Math.Min(p.X, Constants.DungeonWidth - 1), 0);
				p.Y = Math.Max(Math.Min(p.Y, Constants.DungeonHeight - 1), 0);
			}
			return p;
		}

		internal void AddTile(Tile t, Layers layer)
		{
			layers[(int)layer][t.X, t.Y] = t;
		}

		internal Object GetTile(Point point, Layers layer)
		{
			return layers[(int)layer][point.X, point.Y];
		}

		internal Object GetTopTile(Point point)
		{
			for (var i = Enum.GetNames(typeof(Layers)).Count() - 1; i >= 0; i--)
			{
				if (layers[i][point.X, point.Y] != null)
				{
					var tile = layers[i][point.X, point.Y];
					return tile;
				}
			}

			return null;
		}

		internal List<Tile> GetTiles(Point point)
		{
			var listObjects = new List<Tile>();


			for (var i = Enum.GetNames(typeof(Layers)).Count() - 1; i >= 0; i--)
			{
				if (layers[i][point.X, point.Y] != null)
				{
					listObjects.Add(layers[i][point.X, point.Y]);
				}
			}

			return listObjects;
		}

		internal void Clear()
		{
			for (var i = 0; i < Constants.DungeonHeight; i++)
			{
				for (var j = 0; j < Constants.DungeonWidth; j++)
				{
					for (var l = 1; l < Enum.GetNames(typeof(Layers)).Count(); l++)
						layers[l][j, i] = null;
				}
			}
			InitializeTiles();
		}

		internal void AddStairwell(Stairwell stairwell, Layers layer)
		{
			layers[(int)layer][stairwell.X, stairwell.Y] = stairwell;
		}

		public void CreateRandomDungeonObjects()
		{
			GameManager.Instance.CreatureManager.ClearMonsters();
			GameManager.Instance.CreatureManager.AddRandomMonstersByLevel();

			GameManager.Instance.PropManager.ClearProps();
			GameManager.Instance.PropManager.AddProps();
		}

		public Point GetValidRandomPoint(bool callIsOkToPlaceMonster)
		{
			Point p;
			do
			{
				p = new Point(GameManager.Instance.DungeonBuilderRandom.Next(Constants.DungeonWidth), GameManager.Instance.DungeonBuilderRandom.Next(Constants.DungeonWidth));
			}
			while (!IsOKToMove(p));

			if (callIsOkToPlaceMonster)
				if (!IsOKToPlaceMonster(p))
					p = GetValidRandomPoint(true);

			return p;
		}

		public bool IsOKToPlaceMonster(Point p)
		{
			//this might have a problem if an effect is hiding something.
			var topTile = GetTopTile(p);

			var tile = (Tile)topTile;

			return !(topTile is Stairwell | topTile is Player | topTile is Monster | !tile.Walkable);
		}

	}
}
